package tech.cn.note.utils;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.annotation.Nonnull;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

import static org.apache.commons.lang3.StringUtils.isBlank;

@Slf4j
public class DateTimeUtils {
    public static final ZoneId DEFAULT_TIMEZONE = ZoneId.systemDefault();


    /**
     * 以横杠分隔的日期格式：yyyy-M-d
     */
    public static final String HYPHEN_DATE_PATTERN = "yyyy-M-d";

    /**
     * 以横杠分隔的日期格式：yyyy-M-d
     */
    public static final DateTimeFormatter HYPHEN_DATE_FORMATTER = DateTimeFormatter.ofPattern(HYPHEN_DATE_PATTERN);

    /**
     * 以斜杠分隔的日期格式：yyyy/M/d
     */
    public static final String SLASH_DATE_PATTERN = "yyyy/M/d";

    /**
     * 以斜杠分隔的日期格式：yyyy/M/d
     */
    public static final DateTimeFormatter SLASH_DATE_FORMATTER = DateTimeFormatter.ofPattern(SLASH_DATE_PATTERN);

    /**
     * 以横杠分隔的日期格式模式：yyyy-MM-dd
     */
    public static final Pattern HYPHEN_DATE_COMPILE_PATTERN = Pattern.compile("\\d{4}[-]\\d{2}[-]\\d{2}$");

    /**
     * 以横杠分隔的日期格式模式：yyyy-MM-ddTHH:mm:ss
     */
    public static final Pattern HYPHEN_DATETIME_COMPILE_PATTERN = Pattern.compile("\\d{4}[-]\\d{2}[-]\\d{2}[T]\\d{2}[:]\\d{2}[:]\\d{2}$");

    /**
     * 时间格式：yyyy-MM-ddTHH:mm:ss
     */
    public static final Pattern TIME_COMPILE_PATTERN = Pattern.compile("\\d{2}[:]\\d{2}[:]\\d{2}$");

    /**
     * 正则匹配 8 个数字
     */
    public static final Pattern EIGHT_DIGITS_PATTERN = Pattern.compile("\\d{8}");

    public static final Long NANOS_IN_DAY = 24 * 3600 * 1000000000L;

    public static final ZoneId BCT_DEFAULT_TIMEZONE = DEFAULT_TIMEZONE;

    public static String localDateParseToStr(LocalDate localDate) {
        return LocalDateTimeUtil.format(localDate, DatePattern.PURE_DATE_FORMATTER);
    }

    public static String localDateParseToStr(LocalDate localDate, String format) {
        return LocalDateTimeUtil.format(localDate, CharSequenceUtil.removeAll(format, '\'', '\"'));
    }

    public static Period parsePeriod(String tenor) {
        String normalized = tenor.trim().toUpperCase();
        try {
            return Period.parse("P" + normalized);
        } catch (Exception e) {
            throw new RuntimeException("无法解析期限输入： " + tenor);
        }
    }

    public static double days(LocalDateTime start, LocalDateTime end) {
        return start.until(end, ChronoUnit.NANOS) / (double) NANOS_IN_DAY;
    }

    public static double days(LocalDate start, LocalDate end) {
        return start.until(end, ChronoUnit.DAYS);
    }

    public static boolean isWeekend(LocalDate t) {
        return t.getDayOfWeek().getValue() > 5;
    }

    /**
     * 将对象转为 LocalDate，传入 null 或传入字符串为空则返回 null，若无法转换则抛异常
     */
    public static LocalDate toLocalDate(Object inputDate) {
        if (inputDate == null) {
            return null;
        }

        if (inputDate instanceof String) {
            String date = (String) inputDate;
            if (isBlank(date)) {
                return null;
            }

            date = date.replaceAll(" ", "");

            if (date.indexOf('/') == 4) {
                return LocalDate.parse(date, SLASH_DATE_FORMATTER);
            }

            if (date.indexOf('-') == 4) {
                return LocalDate.parse(date, HYPHEN_DATE_FORMATTER);
            }

            if (EIGHT_DIGITS_PATTERN.matcher(date).matches()) {
                return LocalDate.parse(date, DatePattern.PURE_DATE_FORMATTER);
            }

            // 日期部分特别的分隔符都替换为 "-"
            date = date.replaceAll("[.年月。、*|~]", "-");
            date = StrUtil.removeSuffix(date, "日");

            return LocalDate.parse(date, HYPHEN_DATE_FORMATTER);
        }

        if (inputDate instanceof LocalDate) {
            return (LocalDate) inputDate;
        }

        if (inputDate instanceof Date) {
            return ((Date) inputDate).toInstant().atZone(DEFAULT_TIMEZONE).toLocalDate();
        }

        if (inputDate instanceof Instant) {
            return ((Instant) inputDate).atZone(DEFAULT_TIMEZONE).toLocalDate();
        }

        return null;
    }

    /**
     * 将对象转为 LocalDate，传入 null 或传入字符串为空则返回当前日期，若无法转换则抛异常
     */
    @Nonnull
    public static LocalDate toLocalDateOrToday(Object inputDate) {
        return ObjectUtil.defaultIfNull(toLocalDate(inputDate), LocalDate.now());
    }

    /**
     * 将对象转为 LocalDate，传入 null 或传入字符串为空则返回 null，若无法转换也返回 null，不抛异常
     */
    public static LocalDate toLocalDateNoThrow(Object inputDate) {
        try {
            return toLocalDate(inputDate);
        } catch (Exception e) {
            log.debug("{} 转 LocalDate 失败: {}", inputDate, e.getMessage());
            return null;
        }
    }

    /**
     * 将字符串转为 LocalDate，仅匹配以斜杠分隔的日期格式：yyyy/M/d，传入 null 或传入字符串为空则返回 null，若无法转换也返回 null，不抛异常
     */
    public static LocalDate toLocalDateSlashOnly(String inputDate) {
        try {
            if (isBlank(inputDate) || inputDate.indexOf('/') != 4) {
                return null;
            }

            return LocalDate.parse(inputDate, SLASH_DATE_FORMATTER);
        } catch (Exception e) {
            log.debug("{} 以 yyyy/M/d 格式转 LocalDate 失败: {}", inputDate, e.getMessage());
            return null;
        }
    }

    /**
     * 日期字符串是否匹配指定日期格式
     */
    public static Boolean dateMatches(Pattern pattern, String date) {
        return pattern.matcher(date).matches();
    }


    /**
     * 生成起始日到终止日的日期列表
     *
     * @param startDate 起始日
     * @param endDate   终止日
     * @return 日期列表
     */
    public static List<LocalDate> generateDateList(LocalDate startDate, LocalDate endDate) {
        return Stream.iterate(startDate, date -> date.plusDays(1))
                .limit(endDate.toEpochDay() - startDate.toEpochDay() + 1)
                .collect(Collectors.toList());
    }
}
